﻿/* mvcForum
 * Copyright (C) 2010-2011 Steen F. Tøttrup
 * http://mvcforum.org/
 */

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Xml.Linq;

namespace mvcForum.Core {

	/// <summary>
	/// This class represent a topic.
	/// </summary>
	public class Topic : ICustomPropertyHolder {

		#region Constructors
		public Topic() {
			this.SpamReporters = 0;
			this.SpamScore = 0;
			this.ViewCount = 0;
			this.PostCount = 0;
		}
		public Topic(ForumUser author, Forum forum, String subject) {
			this.Forum = forum;
			this.Title = subject;
			this.LastPosted = this.Posted = DateTime.UtcNow;
			this.Author = /*this.LastPostAuthor =*/ author;
			this.AuthorName = /*this.LastPostUsername =*/ author.Name;
			this.SpamReporters = 0;
			this.SpamScore = 0;
			this.Type = TopicType.Regular;
		}
		#endregion

		#region Internal plumbing
		/// <summary>
		/// Method used for setting the latest post.
		/// </summary>
		/// <param name="post">The latest post posted in the topic.</param>
		private void SetLastPost(Post post) {
			this.LastPosted = post.Posted;
			this.LastPost = post;
			this.LastPostAuthor = post.Author;
			this.LastPostUsername = post.AuthorName;
		}

		/// <summary>
		/// Method for clearing the latest post on the topic.
		/// </summary>
		private void ClearLastPost() {
			this.LastPost = null;
			this.LastPostAuthor = null;
			this.LastPostUsername = null;
			this.LastPosted = this.Posted;
		}
		#endregion

		#region Properties
		public Int32 Id { get; set; }
		/// <summary>
		/// The title of the topic.
		/// </summary>
		[StringLength(Constants.FieldLengths.Title)]
		[Required]
		public String Title { get; set; }
		/// <summary>
		/// A count of how many times the topic has been viewed.
		/// </summary>
		[Required]
		public Int32 ViewCount { get; set; }
		/// <summary>
		/// The number of posts in the topic.
		/// </summary>
		[Required]
		public Int32 PostCount { get; set; }
		/// <summary>
		/// The topic flag, ie. quarantined, deleted, etc.
		/// </summary>
		public TopicFlag Flag {
			get {
				return (TopicFlag)this.FlagValue;
			}
			private set {
				this.FlagValue = (Int32)value;
			}
		}
		[Required]
		public Int32 FlagValue { get; set; }
		/// <summary>
		/// The topic type, ie. sitcky, announcement, regular.
		/// </summary>
		public TopicType Type {
			get {
				return (TopicType)this.TypeValue;
			}
			set {
				this.TypeValue = (Int32)value;
			}
		}
		[Required]
		public Int32 TypeValue { get; set; }
		private DateTime lastPosted;
		/// <summary>
		/// The timestamp of the last posted post in the topic.
		/// </summary>
		public DateTime LastPosted {
			get {
				return this.lastPosted;
			}
			set {
				this.lastPosted = value.Handle();
			}
		}
		public Int32? LastPostId { get; set; }
		/// <summary>
		/// A reference to the last post posted in the topic.
		/// </summary>
		public virtual Post LastPost { get; set; }
		public Int32? LastPostAuthorId { get; set; }
		/// <summary>
		/// The author of the topic.
		/// </summary>
		public virtual ForumUser LastPostAuthor { get; set; }
		/// <summary>
		/// The name of the author of the topic.
		/// </summary>
		[StringLength(Constants.FieldLengths.Username)]
		public String LastPostUsername { get; set; }
		[Required]
		public Int32 ForumId { get; set; }
		public virtual Forum Forum { get; set; }
		[Required]
		public Int32 AuthorId { get; set; }
		public virtual ForumUser Author { get; set; }
		[Required]
		[StringLength(Constants.FieldLengths.Username)]
		public String AuthorName { get; set; }
		/// <summary>
		/// Timestamp for when the topic was posted.
		/// </summary>
		private DateTime posted;
		[Required]
		public DateTime Posted {
			get {
				return this.posted;
			}
			set {
				this.posted = value.Handle();
			}
		}
		[Required]
		public Int32 SpamScore { get; set; }
		[Required]
		public Int32 SpamReporters { get; set; }
		public Int32? OriginalTopicId { get; set; }
		public virtual Topic OriginalTopic { get; set; }
		/// <summary>
		/// Do NOT access this property directly. We WILL change the schema from time to time!
		/// Use the extension method on <see cref="ICustomPropertyHolder"/> to get your custom properties.
		/// </summary>
		public String CustomProperties { get; set; }
		/// <summary>
		/// Do NOT access this property directly. We WILL change the schema from time to time!
		/// Use the extension method on <see cref="ICustomPropertyHolder"/> to get your custom properties.
		/// </summary>
		public XDocument CustomData { get; set; }
		#endregion

		#region Public methods
		/// <summary>
		/// 
		/// </summary>
		/// <param name="flag"></param>
		public void SetFlag(TopicFlag flag) {
			// Let's set the flag!
			this.Flag = flag;
			//this.Flag = this.Flag | flag;
			// Let's inform the topic about the change.
			//this.Forum.TopicChanged();
		}

		//public void RemoveFlag(TopicFlag flag) {
		//    // Let's reset the flag!
		//    //this.Flag = this.Flag & ~flag;
		//    this.Flag = this.Flag ^ flag;
		//    // Let's inform the topic about the change.
		//    //this.Forum.TopicChanged();
		//}

		public void Viewed() {
			this.ViewCount++;
		}

		/// <summary>
		/// This method should be used by anti-spam add-ons for reporting results in.
		/// If the add-on is using a score system, instead of a spam/ham system, use this method.
		/// </summary>
		/// <param name="spamScore">The score, in percent, this topic is evaluated to.</param>
		public void ReportSpam(Int32 spamScore) {
			if (spamScore >= 0 && spamScore <= 100) {
				this.SpamReporters++;
				this.SpamScore += SpamScore;
			}
		}

		/// <summary>
		/// Call this method when a post has been added to the topic.
		/// </summary>
		//private void PostAdded() {
		//    IEnumerable<Post> latestPosts = this.Posts.Where(p => (p.Flag & PostFlag.Deleted) == 0 && (p.Flag & PostFlag.Quarantined) == 0).OrderByDescending(p => p.Posted);
		//    Int32 posts = latestPosts.Count();
		//    // Do we have posts?
		//    if (posts > 1) {
		//        // Yes, let's handle that, get the first/latest one!
		//        Post latestPost = latestPosts.First();
		//        // We better update the latest post on this topic!
		//        this.SetLastPost(latestPost);
		//        // First post is position = 0, so:
		//        latestPost.Position = this.Posts.Count - 1;
		//    }
		//    // Let's update the post count, remember first post is actually the topic itself!
		//    this.PostCount = posts - 1;
		//    // Something happened, let's inform the forum.
		//    //this.Forum.TopicChanged();
		//}

		/// <summary>
		/// Call this method if a post in the topic has changed status (PostFlag etc.).
		/// </summary>
		private void PostChanged() {
			// TODO:
			//IEnumerable<Post> posts = this.Posts.Where(p => (p.Flag & PostFlag.Deleted) == 0 && (p.Flag & PostFlag.Quarentined) == 0 && p.Position != 0).OrderByDescending(p => p.Posted);
			//if (posts.Any() && (this.LastPost == null || (this.LastPost != null && posts.First().Id != this.LastPost.Id))) {
			//    this.SetLastPost(posts.First());
			//    this.Forum.TopicChanged();
			//}
			//else if (!posts.Any() && this.LastPost != null) {
			//    this.ClearLastPost();
			//    this.Forum.TopicChanged();
			//}
		}
		#endregion

		/// <summary>
		/// A boolean indicating whether or not a topic is locked.
		/// </summary>
		public Boolean Locked {
			get {
				return ((this.Flag & TopicFlag.Locked) == TopicFlag.Locked);
			}
		}

		/// <summary>
		/// A boolean indicating whether or not a topic is a sticky.
		/// </summary>
		public Boolean Sticky {
			get {
				return this.Type == TopicType.Sticky;
			}
		}

		/// <summary>
		/// A boolean indicating whether or not a topic is an announcement.
		/// </summary>
		public Boolean Announcement {
			get {
				return this.Type == TopicType.Announcement;
			}
		}

		public virtual ICollection<FollowTopic> Followers { get; set; }
		public virtual ICollection<Post> Posts { get; set; }
	}
}